Creating an HTML-based Tab Strip with jQuery UI

Dino Esposito | November 13, 2009

 

Creating complex and compelling user interfaces for the Web has never been easy, and it probably never will be. A complex Web user interface requires enough work on the underlying HTML to compose the graphics you want and enough JavaScript glue code to make it work the way you want. As a developer, you are responsible for both the layout and the code that glues the UI together. More important, you are forced to use tools that overall take you back several years before the ASP.NET revolution.

ASP.NET was revolutionary because it carried developers toward a world of server-side programming in which any client-side functionality was emitted by special black-box components—server controls. The advent of AJAX changed things significantly, as developers rediscovered the importance of performing work on the client through direct contact with HTML and JavaScript.

In this article, I’ll focus on some capabilities of a special library that takes client-side programming power to the next level. The library is jQuery UI, which is a collection of widgets and effects that completes the popular set of features offered by classic jQuery, with a special emphasis on building UIs. In this article, I’ll focus in particular on creating and skinning tabs.

The Importance of Accessing the DOM

When you think about client-side programming, you usually assume that any code will run faster, appear seamless, and produce zero flickering. This is all true, but there’s another, equally important aspect to consider.

Sometimes client-side code in AJAX solutions is limited to downloading some preformatted HTML content that is attached to a DOM element via innerHTML. This is the case, for example, in partial rendering and also for some third-party controls that take advantage of Microsoft’s partial rendering infrastructure. This approach is highly effective in comparison to non-AJAX scenarios, but it still creates a lot of heavy lifting for the browser. When the innerHTML property is set, in fact, the browser is forced to re-create and render again the entire subtree. Normally, browsers are especially quick in processing the innerHTML DOM property, although each browser is different. If you could make your changes directly to the DOM, that would be perfect. The jQuery library helps a lot by providing capabilities for working with DOM elements, especially when it comes to building rich and sophisticated pieces of the user interface.

The best reason to use jQuery UI is that you can define your template according to known rules and then invoke a jQuery UI function on that DOM subtree when the host page is loaded or when you really need it. All of sudden, and quite magically, an otherwise static piece of HTML springs to life and starts reacting to mouse events. Furthermore, the same piece of HTML gets dressed with a new style that results from the great CSS support offered by jQuery UI.

The jQuery UI library is a free download from jqueryui.com. The library is highly modular. When you access the site, you can build your own package by combining the modules you think you will need. You can also link from your pages to a few prebuilt releases hosted on Google’s content delivery network (CDN). Controls in the jQuery UI library need a style library. When you download a package, a default theme is selected that you can freely change to any other you like better.

In the context of an ASP.NET MVC application, you store the CSS files under the Content folder by creating a jq-ui-Themes folder. Script files go under the Scripts\jQueryUI folder, as shown in Figure 1. The jQuery UI library requires the classic jQuery library. The latest version of jQuery UI is 1.7.2; this release goes hand in hand with jQuery version 1.3.2.

Figure 1: The jQuery UI library in action in an ASP.NET MVC project.
Figure 1: The jQuery UI library in action in an ASP.NET MVC project.

Template for Tabs

Let’s see what it takes to create a few tabs for a Web page using jQuery UI. To start off, you create a DOM structure according to the jQuery UI default panel for tabs. By default, the list of tabs belongs to a structure made by a DIV that contains a unordered list (UL). Here’s the required minimal markup:

<div id="MyFirstTabPanel">
  <ul>
  </ul>
</div>

To transform this structure into a JavaScript dynamic tab container object, you need to reference the root element via jQuery and invoke the tabs function on it. This is shown in the following code snippet:

<script type="text/javascript">
    function fnCreateTabs() {
        $("#MyFirstTabPanel").tabs();
    }
</script>

Figure 2 shows the results. Not much to see, to be honest, but there’s enough to believe that something is going on.

Figure 2: An empty tab created on top of a DOM structure.
Figure 2: An empty tab created on top of a DOM structure.

You can invoke the tabs function from the handler of a JavaScript event (for example, a button click handler) or from the ready function that runs on page loading. Most of the time, you just want your tabs to be up and running when the page loads. For testing purposes, though, it might make sense to use button clicks as shown here:

<input type="button" value="Create" onclick="fnCreateTabs()" />

You can add one or more tabs dynamically, or you can flesh out the initial HTML template and provide a bunch of tabs out of the box. Here’s the code you need to create a tab dynamically:

function fnAddTab() {
   $("#MyFirstTabPanel").tabs('add', "/index/about", "Added on the fly", 0);
}

The URL can point to a resource local to the application or to a previously hidden DIV within the page. You can’t use the tab as an IFRAME, however, which means that you can’t download from an external site. Likewise, any absolute link you might follow from a tab will update the browser, not the tab. A DIV within the page is pointed to by specifying it as a hash, in the form of #divID. When you point to a URL outside the page, the tab title is temporarily decorated with a “loading …” message as AJAX is used to download the markup to insert. Through the AjaxOptions settings shown in Table 1 you can customize to a good extent the mechanics of the download.

Figure 3 shows the current results.

Figure 3: A tab container with a dynamically added tab.
Figure 3: A tab container with a dynamically added tab.

In a similar way, you can remove an existing tab and destroy the container as a whole. Here’s the code you need; you identify the tab to remove by index:

function fnDestroyTabs() {
    $("#MyFirstTabPanel").tabs('destroy');
}
function fnRemoveTab() {
    $("#MyFirstTabPanel").tabs('remove', 0);
}

Building a Realistic Tab Container

So much for a few basic tasks. Now let’s explore ways to use a tab’s function more effectively. The first point to consider is how you define the content of each tab. You can point a tab to a publicly reachable URL, or you can point it to a section of the current document. If you point to a publicly reachable URL, the content is downloaded in the background and then inserted in the page. The AJAX infrastructure of classic jQuery is used to download dynamic content from the Web.

A common alternative is to give the tab container a relatively static structure and make each tab point to a child block of the root DIV that the tab container is centered on. The following markup shows a static schema for a tab container:

<div id="AppConsole">
    <ul>
        <li><a href="#tabCustomers"><span><b>Customers</b></span></a></li>
        <li><a href="#tabOrders"><span><b>Orders</b></span></a></li>
        <li><a href="#tabOther"><span><b>Other</b></span></a></li>
    </ul>
    <div id="tabCustomers">
        <span style="font-size:150%"><b>Customers</b></span>
        <hr style="height: 2px;border-top:solid 1px black;" /> 
        :
    </div>
    <div id="tabOrders">
        <span style="font-size:150%"><b>Orders</b></span>
        <hr style="height: 2px;border-top:solid 1px black;" /> 
         :
    </div>
    :
</div>

Each displayed tab is represented with an LI element that contains a child anchor element. Here’s the generic template as it is defined in the library’s source code:

<li><a href="#{href}"><span>#{label}</span></a></li>

When you click a tab, the URL in the anchor is followed and results in in-page navigation if a #-based URL is used.

Any content you want to see displayed when a given tab is selected, if it is not dynamically downloaded from a URL, must be placed as inline markup. At a minimum, this poses the problems of readability and maintenance. In ASP.NET MVC, however, you can work around the issue by using the RenderPartial HTML helper. Here’s how you go about it:

<div id="AppConsole">
    <ul>
        <li><a href="#tabCustomers"><span><b>Customers</b></span></a></li>
        <li><a href="#tabOrders"><span><b>Orders</b></span></a></li>
        <li><a href="#tabOther"><span><b>Other</b></span></a></li>
    </ul>
    <div id="tabCustomers">
        <span style="font-size:150%"><b>Customers</b></span>
        <hr style="height: 2px;border-top:solid 1px black;" /> 
        <%  
            Html.RenderPartial("uc_ListCustomers", someData);
        %>
    </div>
    :
</div>

The Html.RenderPartial helper method simply processes the content of the specified view (specifically, an ASP.NET user control) and emits that markup in place. In this way, you still have static content in the tab container structure, but, at a minimum, you can easily maintain it and also have a much more readable HTML page.

When you intend to use the tab container as a pagewide menu, you typically want the tab strip to be initialized on page loading. The ideal place to do this in a jQuery context is via the ready function, as shown here:

<script type="text/javascript">
    $(document).ready(function() {
        $("#AppConsole").tabs(
            {
                disabled: [2], 
                event: 'mouseover',
                fx: { opacity: 'toggle' }
            }
        );
    });
</script>

As you can see, you can do quite a few things to initialize the tabs function. In particular, you can specify a settings object to drive the appearance and behavior of the tab strip. We’ll look at the details in the next section. Figure 4 shows a much more professional tab container built using jQuery UI and the Sunny theme.

Figure 4: A professional looking tab strip set up with jQuery UI.
Figure 4: A professional looking tab strip set up with jQuery UI.

Settings for the Tab Container

As you’ve seen, the tabs function sets up the container. The function is quite flexible as far as its signature is concerned. For example, you can pass it a settings object as shown earlier. Any settings you pass on startup end up replacing the basic and default settings implicitly defined by the library. Table 1 shows some default values and some parameters you can configure at startup.

Table 1: Properties of the Tab object in jQuery UI

Property Description
disabled Refers to an array of zero-based indexes each corresponding to a tab that is initially displayed as disabled. By default, this is the empty array [].
event Refers to the event to be used to switch between tabs. By default, this is the click event.
fx Refers to a set of animation effects, such as opacity or the duration of show/hide effects. Duration is expressed as slow, fast, or normal.
ajaxOptions Refers to the settings to be used for any AJAX actions aimed at downloading tab content asynchronously.
selectedClass Refers to the CSS class to be picked up from the current theme to style the selected tab.
unselectClass Refers to the CSS class to be picked up from the current theme to style any unselected tabs.
disabledClass Refers to the CSS class to be picked up from the current theme to style any disabled tabs.
panelClass Refers to the CSS class to be picked up from the current theme to style the panel area.
loadingClass Refers to the CSS class to be picked up from the current theme to style the tab while its content is being loaded from the Web.

The internal code of jQuery UI components refers to predefined CSS class names, which the components expect to find in the currently selected theme. Each significant area of the control has its own CSS class name. However, the xxxClass properties shown in Table 1 give you a chance to change their names.

Sending Commands to the Tab Container

In addition to the settings object, the tabs method accepts a few commands for pages to interact with it. The syntax is shown here:

$(tabElement).tabs(command, parameters)

The command is a predefined name recognized by the component. Some commands are parameterless, and others accept a few parameters. Table 2 shows some supported commands.

Table 2: Some commands supported by the tab container

Property Description
destroy Parameterless command; it has the effect of destroying the tabs. After this command is invoked, the display of the DOM tree returns to what it was originally before the tabs method was applied.
add(url, label, index) Adds a new tab to the container. The tab is added to the specified position, with the given label and URL.
select( index) Selects the tab at the specified index. The index is zero-based.
select( id) Selects the tab with a matching ID. The ID is the value of the href attribute on the LI element that represents the tab.
remove(index) Removes the specified tab. Nothing happens if the index doesn't match any existing tab.
disable(index) Disables the specified tab. Nothing happens if the index doesn't match any existing tab.
enable(index) Enables the specified tab. Nothing happens if the index doesn't match any existing tab.
option(name, [value]) Gets (or sets) the value of any option in the tab object. An option is essentially any attribute around the HTML structure you can reach via the jQuery syntax. For example, you can use this feature to select a tab or to get the index of the selected tab. Likewise, you can perform a special query to pick only a few that match a given criterion.
load(index) Loads the content of the specified tab.

Tab Events

A component fires a number of events for the main situations you face when working. For example, an event occurs when a tab is selected, added, or removed. Likewise, an event is fired when the tab is shown, enabled or disabled, or loaded. Table 3 lists the events supported:

Table 3: Events supported by the tab container

Event Description
tabsadd Fires after a tab is added.
tabsdisable Fires after a given tab is disabled.
tabsenable Fires after a given tab is enabled.
tabsload Fires after the content of a tab has been loaded from a remote location (via AJAX).
tabsremove Fires after a given tab is removed.
tabsselect Fires after a given tab is selected.
tabsshow Fires after a given tab is shown.

All event handlers receive an object that represents a container for tab-related information. Here’s the signature of the event handler:

function(event, ui) 
{
    :
}

From the ui parameter, you can retrieve various pieces of more specific information such as that listed in Table 4.

Table 4: Information available to event handlers.

Property Description
ui.index Returns the zero-based index of the tab involved with the event. For example, for the tabsadd event, this will be the index of the newly added tab.
ui.panel Returns the element that wraps the contents of the tab involved with the event. For example, for the tabsselect event, this will be DIV, where the contents of the tab are contained.
ui.tab Returns the anchor element of the tab involved in the event.

Dealing with Tab Selection

More often than not, a user selects a tab in a container by clicking it or, perhaps, by hovering the mouse over the selected tab. It is useful, however, to have the ability to select a tab programmatically. The select command serves just this purpose:

$("#MyTabContainer").tabs('select', 2);

The preceding code selects the third tab (if any). The index, obviously, is zero-based.

It is even more helpful sometimes to be able to retrieve the index of the currently selected tab, as shown here:

var index = $("#MyTabContainer").tabs('option', 'selected');

In this case, you proceed by invoking the option command and reading the index where the selected attribute is set.

Another interesting capability that relates to selection is preventing a switch to another tab if the conditions on the currently selected tab don’t allow for that. All you need to do to prevent a change of selection is to handle the tabsselect event, evaluate the state of the application, and return false from the handler.

Loading Content via AJAX

When you plan a tab strip, you typically have some UI content to break into many, and more usable, parts. The entire markup is downloaded with the page and built into the DOM. This means that most of the time, you know the content of the tabs already when you display tabs. In this regard, hiding a few tabs and displaying them on demand may be a way to improve the user’s experience; it can hardly be a way to improve the performance. On the other hand, using the AJAX loader ensures that you download markup and enlarge the DOM only on a strict demand.

You can download and display content automatically by setting the URL of a new tab to a same-domain URL. The load method takes the index of the tab and downloads content for it from the Web server. Fresh content is always downloaded regardless of the browser cache. Here is an example:

$("#MyTabContainer").tabs('load', 2);

As an alternative, you can control the process yourself and use the jQuery AJAX API to make the connection, grab data, and then use the DOM API to attach it to a previously empty tab.

Summary

Having tabs in a Web interface is fairly common today. Most of the time, tabs are coded by loading segments of the page and attaching them to mouse events. When a particular link is clicked, the associated content is displayed. From a browser perspective, all the work is done when the page is first loaded. After that, it is all about changing some visibility attributes. The jQuery UI library allows you to build a tab strip out of some DIV tags and LI elements you might already have on the page. It’s as though these elements are styled to look like a tab strip, except that the style also includes some special behavior. To a good extent, tabs can also be manipulated dynamically. For me, however, the simplicity and effectiveness of the setup remains the most attractive trait.

 

About the Author

Dino is the author of "Programming ASP.NET MVC" for Microsoft Press and also coauthored the bestseller "Microsoft .NET: Architecting Applications for the Enterprise" (Microsoft Press 2008). A long time author and experienced consultant and trainer, Dino lives in Italy (when not traveling) and plays tennis (when not injured).